home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Chat & Communication / Digsby build 37 / digsby_setup.exe / lib / lxml / html / diff.pyo (.txt) < prev    next >
Python Compiled Bytecode  |  2008-10-13  |  18KB  |  637 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyo (Python 2.5)
  3.  
  4. import difflib
  5. from lxml import etree
  6. from lxml.html import fragment_fromstring
  7. import cgi
  8. import re
  9. __all__ = [
  10.     'html_annotate',
  11.     'htmldiff']
  12.  
  13. try:
  14.     _unicode = unicode
  15. except NameError:
  16.     _unicode = str
  17.  
  18.  
  19. try:
  20.     basestring = __builtins__['basestring']
  21. except (KeyError, NameError):
  22.     basestring = str
  23.  
  24.  
  25. def default_markup(text, version):
  26.     return '<span title="%s">%s</span>' % (cgi.escape(_unicode(version), 1), text)
  27.  
  28.  
  29. def html_annotate(doclist, markup = default_markup):
  30.     tokenlist = [ tokenize_annotated(doc, version) for doc, version in doclist ]
  31.     cur_tokens = tokenlist[0]
  32.     for tokens in tokenlist[1:]:
  33.         html_annotate_merge_annotations(cur_tokens, tokens)
  34.         cur_tokens = tokens
  35.     
  36.     cur_tokens = compress_tokens(cur_tokens)
  37.     result = markup_serialize_tokens(cur_tokens, markup)
  38.     return ''.join(result).strip()
  39.  
  40.  
  41. def tokenize_annotated(doc, annotation):
  42.     tokens = tokenize(doc, include_hrefs = False)
  43.     for tok in tokens:
  44.         tok.annotation = annotation
  45.     
  46.     return tokens
  47.  
  48.  
  49. def html_annotate_merge_annotations(tokens_old, tokens_new):
  50.     s = InsensitiveSequenceMatcher(a = tokens_old, b = tokens_new)
  51.     commands = s.get_opcodes()
  52.     for command, i1, i2, j1, j2 in commands:
  53.         if command == 'equal':
  54.             eq_old = tokens_old[i1:i2]
  55.             eq_new = tokens_new[j1:j2]
  56.             copy_annotations(eq_old, eq_new)
  57.             continue
  58.     
  59.  
  60.  
  61. def copy_annotations(src, dest):
  62.     for src_tok, dest_tok in zip(src, dest):
  63.         dest_tok.annotation = src_tok.annotation
  64.     
  65.  
  66.  
  67. def compress_tokens(tokens):
  68.     result = [
  69.         tokens[0]]
  70.     for tok in tokens[1:]:
  71.         if not (result[-1].post_tags) and not (tok.pre_tags) and result[-1].annotation == tok.annotation:
  72.             compress_merge_back(result, tok)
  73.             continue
  74.         result.append(tok)
  75.     
  76.     return result
  77.  
  78.  
  79. def compress_merge_back(tokens, tok):
  80.     last = tokens[-1]
  81.     if type(last) is not token or type(tok) is not token:
  82.         tokens.append(tok)
  83.     else:
  84.         text = _unicode(last)
  85.         if last.trailing_whitespace:
  86.             text += ' '
  87.         
  88.         text += tok
  89.         merged = token(text, pre_tags = last.pre_tags, post_tags = tok.post_tags, trailing_whitespace = tok.trailing_whitespace)
  90.         merged.annotation = last.annotation
  91.         tokens[-1] = merged
  92.  
  93.  
  94. def markup_serialize_tokens(tokens, markup_func):
  95.     for token in tokens:
  96.         for pre in token.pre_tags:
  97.             yield pre
  98.         
  99.         html = token.html()
  100.         html = markup_func(html, token.annotation)
  101.         if token.trailing_whitespace:
  102.             html += ' '
  103.         
  104.         yield html
  105.         for post in token.post_tags:
  106.             yield post
  107.         
  108.     
  109.  
  110.  
  111. def htmldiff(old_html, new_html):
  112.     old_html_tokens = tokenize(old_html)
  113.     new_html_tokens = tokenize(new_html)
  114.     result = htmldiff_tokens(old_html_tokens, new_html_tokens)
  115.     result = ''.join(result).strip()
  116.     return fixup_ins_del_tags(result)
  117.  
  118.  
  119. def htmldiff_tokens(html1_tokens, html2_tokens):
  120.     s = InsensitiveSequenceMatcher(a = html1_tokens, b = html2_tokens)
  121.     commands = s.get_opcodes()
  122.     result = []
  123.     for command, i1, i2, j1, j2 in commands:
  124.         if command == 'equal':
  125.             result.extend(expand_tokens(html2_tokens[j1:j2], equal = True))
  126.             continue
  127.         
  128.         if command == 'insert' or command == 'replace':
  129.             ins_tokens = expand_tokens(html2_tokens[j1:j2])
  130.             merge_insert(ins_tokens, result)
  131.         
  132.         if command == 'delete' or command == 'replace':
  133.             del_tokens = expand_tokens(html1_tokens[i1:i2])
  134.             merge_delete(del_tokens, result)
  135.             continue
  136.     
  137.     result = cleanup_delete(result)
  138.     return result
  139.  
  140.  
  141. def expand_tokens(tokens, equal = False):
  142.     for token in tokens:
  143.         for pre in token.pre_tags:
  144.             yield pre
  145.         
  146.         if not equal or not (token.hide_when_equal):
  147.             if token.trailing_whitespace:
  148.                 yield token.html() + ' '
  149.             else:
  150.                 yield token.html()
  151.         
  152.         for post in token.post_tags:
  153.             yield post
  154.         
  155.     
  156.  
  157.  
  158. def merge_insert(ins_chunks, doc):
  159.     (unbalanced_start, balanced, unbalanced_end) = split_unbalanced(ins_chunks)
  160.     doc.extend(unbalanced_start)
  161.     if doc and not doc[-1].endswith(' '):
  162.         doc[-1] += ' '
  163.     
  164.     doc.append('<ins>')
  165.     if balanced and balanced[-1].endswith(' '):
  166.         balanced[-1] = balanced[-1][:-1]
  167.     
  168.     doc.extend(balanced)
  169.     doc.append('</ins> ')
  170.     doc.extend(unbalanced_end)
  171.  
  172.  
  173. class DEL_START:
  174.     pass
  175.  
  176.  
  177. class DEL_END:
  178.     pass
  179.  
  180.  
  181. class NoDeletes(Exception):
  182.     pass
  183.  
  184.  
  185. def merge_delete(del_chunks, doc):
  186.     doc.append(DEL_START)
  187.     doc.extend(del_chunks)
  188.     doc.append(DEL_END)
  189.  
  190.  
  191. def cleanup_delete(chunks):
  192.     while None:
  193.         
  194.         try:
  195.             (pre_delete, delete, post_delete) = split_delete(chunks)
  196.         except NoDeletes:
  197.             break
  198.  
  199.         (unbalanced_start, balanced, unbalanced_end) = split_unbalanced(delete)
  200.         locate_unbalanced_end(unbalanced_end, pre_delete, post_delete)
  201.         doc = pre_delete
  202.         if doc and not doc[-1].endswith(' '):
  203.             doc[-1] += ' '
  204.         
  205.         doc.append('<del>')
  206.         if balanced and balanced[-1].endswith(' '):
  207.             balanced[-1] = balanced[-1][:-1]
  208.         
  209.         doc.extend(balanced)
  210.         doc.append('</del> ')
  211.         doc.extend(post_delete)
  212.         chunks = doc
  213.         continue
  214.         return chunks
  215.  
  216.  
  217. def split_unbalanced(chunks):
  218.     start = []
  219.     end = []
  220.     tag_stack = []
  221.     balanced = []
  222.     for chunk in chunks:
  223.         if not chunk.startswith('<'):
  224.             balanced.append(chunk)
  225.             continue
  226.         
  227.         endtag = chunk[1] == '/'
  228.         name = chunk.split()[0].strip('<>/')
  229.         if name in empty_tags:
  230.             balanced.append(chunk)
  231.             continue
  232.         
  233.         if endtag:
  234.             if tag_stack and tag_stack[-1][0] == name:
  235.                 balanced.append(chunk)
  236.                 (name, pos, tag) = tag_stack.pop()
  237.                 balanced[pos] = tag
  238.             elif tag_stack:
  239.                 []([ tag for name, pos, tag in tag_stack ])
  240.                 tag_stack = []
  241.                 end.append(chunk)
  242.             else:
  243.                 end.append(chunk)
  244.         tag_stack[-1][0] == name
  245.         tag_stack.append((name, len(balanced), chunk))
  246.         balanced.append(None)
  247.     
  248.     []([ chunk for name, pos, chunk in tag_stack ])
  249.     balanced = _[3]
  250.     return (start, balanced, end)
  251.  
  252.  
  253. def split_delete(chunks):
  254.     
  255.     try:
  256.         pos = chunks.index(DEL_START)
  257.     except ValueError:
  258.         raise NoDeletes
  259.  
  260.     pos2 = chunks.index(DEL_END)
  261.     return (chunks[:pos], chunks[pos + 1:pos2], chunks[pos2 + 1:])
  262.  
  263.  
  264. def locate_unbalanced_start(unbalanced_start, pre_delete, post_delete):
  265.     while not unbalanced_start:
  266.         break
  267.     finding = unbalanced_start[0]
  268.     finding_name = finding.split()[0].strip('<>')
  269.     if not post_delete:
  270.         break
  271.     
  272.     next = post_delete[0]
  273.     if next is DEL_START or not next.startswith('<'):
  274.         break
  275.     
  276.     if next[1] == '/':
  277.         break
  278.     
  279.     name = next.split()[0].strip('<>')
  280.     if name == 'ins':
  281.         break
  282.     
  283.     if name == finding_name:
  284.         unbalanced_start.pop(0)
  285.         pre_delete.append(post_delete.pop(0))
  286.         continue
  287.     break
  288.     continue
  289.  
  290.  
  291. def locate_unbalanced_end(unbalanced_end, pre_delete, post_delete):
  292.     while not unbalanced_end:
  293.         break
  294.     finding = unbalanced_end[-1]
  295.     finding_name = finding.split()[0].strip('<>/')
  296.     if not pre_delete:
  297.         break
  298.     
  299.     next = pre_delete[-1]
  300.     if next is DEL_END or not next.startswith('</'):
  301.         break
  302.     
  303.     name = next.split()[0].strip('<>/')
  304.     if name == 'ins' or name == 'del':
  305.         break
  306.     
  307.     if name == finding_name:
  308.         unbalanced_end.pop()
  309.         post_delete.insert(0, pre_delete.pop())
  310.         continue
  311.     break
  312.     continue
  313.  
  314.  
  315. class token(_unicode):
  316.     hide_when_equal = False
  317.     
  318.     def __new__(cls, text, pre_tags = None, post_tags = None, trailing_whitespace = False):
  319.         obj = _unicode.__new__(cls, text)
  320.         if pre_tags is not None:
  321.             obj.pre_tags = pre_tags
  322.         else:
  323.             obj.pre_tags = []
  324.         if post_tags is not None:
  325.             obj.post_tags = post_tags
  326.         else:
  327.             obj.post_tags = []
  328.         obj.trailing_whitespace = trailing_whitespace
  329.         return obj
  330.  
  331.     
  332.     def __repr__(self):
  333.         return 'token(%s, %r, %r)' % (_unicode.__repr__(self), self.pre_tags, self.post_tags)
  334.  
  335.     
  336.     def html(self):
  337.         return _unicode(self)
  338.  
  339.  
  340.  
  341. class tag_token(token):
  342.     
  343.     def __new__(cls, tag, data, html_repr, pre_tags = None, post_tags = None, trailing_whitespace = False):
  344.         obj = token.__new__(cls, '%s: %s' % (type, data), pre_tags = pre_tags, post_tags = post_tags, trailing_whitespace = trailing_whitespace)
  345.         obj.tag = tag
  346.         obj.data = data
  347.         obj.html_repr = html_repr
  348.         return obj
  349.  
  350.     
  351.     def __repr__(self):
  352.         return 'tag_token(%s, %s, html_repr=%s, post_tags=%r, pre_tags=%r, trailing_whitespace=%s)' % (self.tag, self.data, self.html_repr, self.pre_tags, self.post_tags, self.trailing_whitespace)
  353.  
  354.     
  355.     def html(self):
  356.         return self.html_repr
  357.  
  358.  
  359.  
  360. class href_token(token):
  361.     hide_when_equal = True
  362.     
  363.     def html(self):
  364.         return 'Link: %s' % self
  365.  
  366.  
  367.  
  368. def tokenize(html, include_hrefs = True):
  369.     body_el = parse_html(html, cleanup = True)
  370.     chunks = flatten_el(body_el, skip_tag = True, include_hrefs = include_hrefs)
  371.     return fixup_chunks(chunks)
  372.  
  373.  
  374. def parse_html(html, cleanup = True):
  375.     if cleanup:
  376.         html = cleanup_html(html)
  377.     
  378.     return fragment_fromstring(html, create_parent = True)
  379.  
  380. _body_re = re.compile('<body.*?>', re.I | re.S)
  381. _end_body_re = re.compile('</body.*?>', re.I | re.S)
  382. _ins_del_re = re.compile('</?(ins|del).*?>', re.I | re.S)
  383.  
  384. def cleanup_html(html):
  385.     match = _body_re.search(html)
  386.     if match:
  387.         html = html[match.end():]
  388.     
  389.     match = _end_body_re.search(html)
  390.     if match:
  391.         html = html[:match.start()]
  392.     
  393.     html = _ins_del_re.sub('', html)
  394.     return html
  395.  
  396. end_whitespace_re = re.compile('[ \\t\\n\\r]$')
  397.  
  398. def fixup_chunks(chunks):
  399.     tag_accum = []
  400.     cur_word = None
  401.     result = []
  402.     for chunk in chunks:
  403.         if isinstance(chunk, tuple):
  404.             if chunk[0] == 'img':
  405.                 src = chunk[1]
  406.                 tag = chunk[2]
  407.                 if tag.endswith(' '):
  408.                     tag = tag[:-1]
  409.                     trailing_whitespace = True
  410.                 else:
  411.                     trailing_whitespace = False
  412.                 cur_word = tag_token('img', src, html_repr = tag, pre_tags = tag_accum, trailing_whitespace = trailing_whitespace)
  413.                 tag_accum = []
  414.                 result.append(cur_word)
  415.                 continue
  416.             if chunk[0] == 'href':
  417.                 href = chunk[1]
  418.                 cur_word = href_token(href, pre_tags = tag_accum, trailing_whitespace = True)
  419.                 tag_accum = []
  420.                 result.append(cur_word)
  421.                 continue
  422.             continue
  423.         
  424.         if is_word(chunk):
  425.             if chunk.endswith(' '):
  426.                 chunk = chunk[:-1]
  427.                 trailing_whitespace = True
  428.             else:
  429.                 trailing_whitespace = False
  430.             cur_word = token(chunk, pre_tags = tag_accum, trailing_whitespace = trailing_whitespace)
  431.             tag_accum = []
  432.             result.append(cur_word)
  433.             continue
  434.         None if is_start_tag(chunk) else tag_accum
  435.     
  436.     if not result:
  437.         return [
  438.             token('', pre_tags = tag_accum)]
  439.     else:
  440.         result[-1].post_tags.extend(tag_accum)
  441.     return result
  442.  
  443. empty_tags = ('param', 'img', 'area', 'br', 'basefont', 'input', 'base', 'meta', 'link', 'col')
  444. block_level_tags = ('address', 'blockquote', 'center', 'dir', 'div', 'dl', 'fieldset', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'isindex', 'menu', 'noframes', 'noscript', 'ol', 'p', 'pre', 'table', 'ul')
  445. block_level_container_tags = ('dd', 'dt', 'frameset', 'li', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr')
  446.  
  447. def flatten_el(el, include_hrefs, skip_tag = False):
  448.     if not skip_tag:
  449.         if el.tag == 'img':
  450.             yield ('img', el.attrib['src'], start_tag(el))
  451.         else:
  452.             yield start_tag(el)
  453.     
  454.     if el.tag in empty_tags and not (el.text) and not len(el) and not (el.tail):
  455.         return None
  456.     
  457.     start_words = split_words(el.text)
  458.     for word in start_words:
  459.         yield cgi.escape(word)
  460.     
  461.     for child in el:
  462.         for item in flatten_el(child, include_hrefs = include_hrefs):
  463.             yield item
  464.         
  465.     
  466.     if el.tag == 'a' and el.attrib.get('href') and include_hrefs:
  467.         yield ('href', el.attrib['href'])
  468.     
  469.     if not skip_tag:
  470.         yield end_tag(el)
  471.         end_words = split_words(el.tail)
  472.         for word in end_words:
  473.             yield cgi.escape(word)
  474.         
  475.     
  476.  
  477.  
  478. def split_words(text):
  479.     if not text or not text.strip():
  480.         return []
  481.     
  482.     words = [ w + ' ' for w in text.strip().split() ]
  483.     return words
  484.  
  485. start_whitespace_re = re.compile('^[ \\t\\n\\r]')
  486.  
  487. def start_tag(el):
  488.     return ''.join % ([], []([ ' %s="%s"' % (name, cgi.escape(value, True)) for name, value in el.attrib.items() ]))
  489.  
  490.  
  491. def end_tag(el):
  492.     if el.tail and start_whitespace_re.search(el.tail):
  493.         extra = ' '
  494.     else:
  495.         extra = ''
  496.     return '</%s>%s' % (el.tag, extra)
  497.  
  498.  
  499. def is_word(tok):
  500.     return not tok.startswith('<')
  501.  
  502.  
  503. def is_end_tag(tok):
  504.     return tok.startswith('</')
  505.  
  506.  
  507. def is_start_tag(tok):
  508.     if tok.startswith('<'):
  509.         pass
  510.     return not tok.startswith('</')
  511.  
  512.  
  513. def fixup_ins_del_tags(html):
  514.     doc = parse_html(html, cleanup = False)
  515.     _fixup_ins_del_tags(doc)
  516.     html = serialize_html_fragment(doc, skip_outer = True)
  517.     return html
  518.  
  519.  
  520. def serialize_html_fragment(el, skip_outer = False):
  521.     html = etree.tostring(el, method = 'html', encoding = _unicode)
  522.     if skip_outer:
  523.         html = html[html.find('>') + 1:]
  524.         html = html[:html.rfind('<')]
  525.         return html.strip()
  526.     else:
  527.         return html
  528.  
  529.  
  530. def _fixup_ins_del_tags(doc):
  531.     for tag in [
  532.         'ins',
  533.         'del']:
  534.         for el in doc.xpath('descendant-or-self::%s' % tag):
  535.             if not _contains_block_level_tag(el):
  536.                 continue
  537.             
  538.             _move_el_inside_block(el, tag = tag)
  539.             el.drop_tag()
  540.         
  541.     
  542.  
  543.  
  544. def _contains_block_level_tag(el):
  545.     if el.tag in block_level_tags or el.tag in block_level_container_tags:
  546.         return True
  547.     
  548.     for child in el:
  549.         if _contains_block_level_tag(child):
  550.             return True
  551.             continue
  552.     
  553.     return False
  554.  
  555.  
  556. def _move_el_inside_block(el, tag):
  557.     for child in el:
  558.         if _contains_block_level_tag(child):
  559.             break
  560.             continue
  561.     else:
  562.         import sys as sys
  563.         children_tag = etree.Element(tag)
  564.         children_tag.text = el.text
  565.         el.text = None
  566.         el[:] = [
  567.             children_tag]
  568.         return None
  569.     for child in list(el):
  570.         if _contains_block_level_tag(child):
  571.             _move_el_inside_block(child, tag)
  572.             if child.tail:
  573.                 tail_tag = etree.Element(tag)
  574.                 tail_tag.text = child.tail
  575.                 child.tail = None
  576.                 el.insert(el.index(child) + 1, tail_tag)
  577.             
  578.         child.tail
  579.         child_tag = etree.Element(tag)
  580.         el.replace(child, child_tag)
  581.         child_tag.append(child)
  582.     
  583.     if el.text:
  584.         text_tag = etree.Element(tag)
  585.         text_tag.text = el.text
  586.         el.text = None
  587.         el.insert(0, text_tag)
  588.     
  589.  
  590.  
  591. def _merge_element_contents(el):
  592.     parent = el.getparent()
  593.     if not el.text:
  594.         pass
  595.     text = ''
  596.     if el.tail:
  597.         if not len(el):
  598.             text += el.tail
  599.         elif el[-1].tail:
  600.             el[-1].tail += el.tail
  601.         else:
  602.             el[-1].tail = el.tail
  603.     
  604.     index = parent.index(el)
  605.     if text:
  606.         if index == 0:
  607.             previous = None
  608.         else:
  609.             previous = parent[index - 1]
  610.         if previous is None:
  611.             if parent.text:
  612.                 parent.text += text
  613.             else:
  614.                 parent.text = text
  615.         elif previous.tail:
  616.             previous.tail += text
  617.         else:
  618.             previous.tail = text
  619.     
  620.     parent[index:index + 1] = el.getchildren()
  621.  
  622.  
  623. class InsensitiveSequenceMatcher(difflib.SequenceMatcher):
  624.     threshold = 2
  625.     
  626.     def get_matching_blocks(self):
  627.         size = min(len(self.b), len(self.b))
  628.         threshold = min(self.threshold, size / 4)
  629.         actual = difflib.SequenceMatcher.get_matching_blocks(self)
  630.         return _[1]
  631.  
  632.  
  633. if __name__ == '__main__':
  634.     from lxml.html import _diffcommand
  635.     _diffcommand.main()
  636.  
  637.